Unsupervised RNA-seq analysis

Author

Cordeliers Artificial Intelligence and Bioinformatics

Published

May 5, 2025

library(airway)
Loading required package: SummarizedExperiment
Loading required package: MatrixGenerics
Loading required package: matrixStats

Attaching package: 'MatrixGenerics'
The following objects are masked from 'package:matrixStats':

    colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse,
    colCounts, colCummaxs, colCummins, colCumprods, colCumsums,
    colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs,
    colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats,
    colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
    colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads,
    colWeightedMeans, colWeightedMedians, colWeightedSds,
    colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
    rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
    rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps,
    rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
    rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks,
    rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
    rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
    rowWeightedSds, rowWeightedVars
Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics
Loading required package: generics

Attaching package: 'generics'
The following objects are masked from 'package:base':

    as.difftime, as.factor, as.ordered, intersect, is.element, setdiff,
    setequal, union

Attaching package: 'BiocGenerics'
The following objects are masked from 'package:stats':

    IQR, mad, sd, var, xtabs
The following objects are masked from 'package:base':

    anyDuplicated, aperm, append, as.data.frame, basename, cbind,
    colnames, dirname, do.call, duplicated, eval, evalq, Filter, Find,
    get, grep, grepl, is.unsorted, lapply, Map, mapply, match, mget,
    order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank,
    rbind, Reduce, rownames, sapply, saveRDS, table, tapply, unique,
    unsplit, which.max, which.min
Loading required package: S4Vectors

Attaching package: 'S4Vectors'
The following object is masked from 'package:utils':

    findMatches
The following objects are masked from 'package:base':

    expand.grid, I, unname
Loading required package: IRanges
Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with
    'browseVignettes()'. To cite Bioconductor, see
    'citation("Biobase")', and for packages 'citation("pkgname")'.

Attaching package: 'Biobase'
The following object is masked from 'package:MatrixGenerics':

    rowMedians
The following objects are masked from 'package:matrixStats':

    anyMissing, rowMedians
library(SummarizedExperiment)
species <- "Homo sapiens"

# Variable d'annotation à visualiser (colData(airway))
plot_annotations <- "dex"  # traitement au dexaméthasone

# Paramètres de qualité (QC)
qc_min_nsamp <- 2
qc_min_gene_counts <- 10  # valeur un peu plus élevée pour un jeu réel

# Clustering d'expression
exp_cluster <- data.frame(k = 2)

# Clustering des métadonnées
metadata_clusters <- list(
  pathway_scores = data.frame(k = 2),
  microenv_scores = data.frame(k = 3)
)


# Collections de pathways (humaines)
pathway_collections <- "CP:KEGG_LEGACY"  # KEGG tout court est plus standard pour Homo sapiens

# Gènes pertinents dans airway pour les heatmaps (liés au métabolisme, réponse au stress, etc.)
heatmap_genes <- list(
  c("NR3C1", "FKBP5", "TSC22D3", "ZBTB16", "PER1"),  # gènes régulés par le glucocorticoïde
  c("CYP1B1", "G6PD", "HMOX1", "NQO1", "SOD2")       # stress oxydatif / métabolisme
)

# Exemples de pathways (à adapter selon les résultats d'analyse)
heatmap_pathways <- c(
  "KEGG_APOPTOSIS",
  "KEGG_GLUTATHIONE_METABOLISM"
)

# Gènes pour les boxplots (fortement exprimés et différentiels)
boxplot_genes <- c("FKBP5", "TSC22D3")

# Pathways pour les boxplots
boxplot_pathways <- c(
  "KEGG_APOPTOSIS",
  "KEGG_GLUTATHIONE_METABOLISM"
)

# Corrélations entre gènes
correlation_genes <- list(
  c("FKBP5", "TSC22D3"),
  c("FKBP5", "ZBTB16")
)

# Corrélations entre pathways
correlation_pathways <- list(
  c("KEGG_APOPTOSIS", "KEGG_GLUTATHIONE_METABOLISM"),
  c("KEGG_APOPTOSIS", "KEGG_NOD_LIKE_RECEPTOR_SIGNALING_PATHWAY")
)
library(CAIBIrnaseq)

Load data

This section loads the RNA-seq dataset for analysis. It ensures the correct input file is used, as specified in the parameters. rebase_gexp

data(airway, package="airway")
exp_data <- airway

rowData(exp_data)$gene_length_kb <- 
  (rowData(exp_data)$gene_seq_end - rowData(exp_data)$gene_seq_start) / 1000

library(biomaRt)

# Initialiser biomaRt pour Ensembl humain
mart <- useMart("ensembl", dataset = "hsapiens_gene_ensembl")

# Obtenir les descriptions basées sur les gene_id
gene_ids <- rowData(exp_data)$gene_id

annot <- getBM(attributes = c("ensembl_gene_id", "description"),
               filters = "ensembl_gene_id",
               values = gene_ids,
               mart = mart)

# Faire correspondre les descriptions aux lignes de rowData
matched <- match(rowData(exp_data)$gene_id, annot$ensembl_gene_id)
rowData(exp_data)$gene_description <- annot$description[matched]

Pre-processing

Most datasets use ensemble gene ID by default after alignment, so this step rebases the expression data to gene names. This ensures consistency in naming for downstream analyses.

exp_data <- rebase_gexp(exp_data, annotation = "gene_name")
-- Rebasing the gene expression matrix using `gene_name` as main annotation

Filter

Here, we filter out genes expressed in too few samples or with very low counts. This removes noise from the data and focuses on meaningful gene expressions.

exp_data <- filter_gexp(exp_data,
                        min_nsamp = 1, 
                        min_counts = 1)
- Keeping 33026/56638 genes found in at least 1 sample(s) with at least 1 counts

diffexp <- diffExpAnalysis(countData = SummarizedExperiment::assays(exp_data)$counts, sampleInfo = SummarizedExperiment::colData(exp_data), method = diffexpMethod, cutoff = 10, design, coefname) Visualization of the filtering process to ensure the criteria applied align with the dataset’s characteristics:

colData(exp_data)$sample_id <- colnames(exp_data)
plot_qc_filters(exp_data)
Saving 7 x 5 in image
Warning: Removed 8 rows containing missing values or values outside the scale range
(`geom_text_repel()`).
Warning in geom2trace.default(dots[[1L]][[1L]], dots[[2L]][[1L]], dots[[3L]][[1L]]): geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues

Normalize

Here, we apply a normalization to the expression data, making samples comparable by reducing variability due to technical differences. For datasets with few samples, rlog is the preferred normalization and when more samples are present, vst is applied. class(exp_data)

exp_data <- normalize_gexp(exp_data)
- Less than 30 samples -> Performing `rlog` normalization...

PCA

Principal component analysis (PCA) identifies the major patterns in the dataset. These patterns help explore similarities or differences among samples based on gene expression.

pca_res = pca_gexp(exp_data)
exp_data@metadata[["pca_res"]] <- pca_res

annotations <- setdiff(plot_annotations, c("exp_cluster", "path_cluster"))
plot_pca(exp_data, color = plot_annotations)
Saving 7 x 5 in image
library(factoextra)
Loading required package: ggplot2
Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
groups <- SummarizedExperiment::colData(exp_data)$dex  # ou le nom réel de la colonne indiquant STING_KO/Wild_type

fviz_pca_ind(pca_res,
             geom = "point",
             habillage = groups,
             palette = c("#00AFBB", "#E7B800"),  # couleurs personnalisées
             addEllipses = TRUE,
             ellipse.type = "confidence",
             repel = TRUE,
             label = "none"
)

Unsupervised clustering

Here, we group samples based on expression patterns without prior knowledge using hierarchical clustering on either a selected gene list from the parameters or, by default, the 2000 most highly expressed genes.

This can be useful for discovering sample subgroups or new biological insights.

exp_data <- cluster_exp(exp_data, k = exp_cluster$k, genes = exp_cluster$genes, n_pcs = 3)
Clustering based on the top 2000 highly variable genes.
-- Reducing dimensionality using PCA
-- Performing hierarchical clustering into 2 groups

Visual representation of expression levels for HVG across clusters, highlighting distinct patterns.

hvg <- highly_variable_genes(exp_data)
exp_cluster <- data.frame(k = 2)
hm <- plot_exp_heatmap(exp_data,  genes = hvg, 
                 annotations = c(plot_annotations, "exp_cluster"),
                 show_rownames = FALSE,
                 hm_color_limits = c(-2,2),
                 fname = "results/clustering/heatmap_2000hvg_exp_cluster.pdf")
-- Saving heatmap at results/clustering/heatmap_2000hvg_exp_cluster.pdf

Pathway activity

Pathway analysis enables us to understand the functional implications of gene expression changes. Here, we analyze the dataset for pathway activity using two methods.

PROGENy

PROGENy is a collection of only 14 core pathway responsive genes from large signaling perturbation experiments. For more information see the original paper.

progeny_scores <- score_progeny(exp_data, species = "Homo sapiens")

tmp <- S4Vectors::metadata(exp_data)          # récupère une liste depuis exp_data
tmp[["progeny_scores"]] <- progeny_scores  # modifie cette liste
S4Vectors::metadata(exp_data) <- tmp  

plot_progeny_heatmap(exp_data, annotations = plot_annotations,
                     fname = "results/pathways/hm_progeny_scores.pdf")
Warning in prep_scores_hm(exp_data, progeny_scores): 'sample_id' already exists
in colData and will be overwritten.
-- Saving heatmap at results/pathways/hm_progeny_scores.pdf

write.csv(progeny_scores, file = "results/pathways/progeny_scores.csv")

Pathways

Pathway collections available in the MSIGdb can be specified in the parameters. These pathways are scored and ranked by their variance in the data. These are the available collections (use gs_subcat as name except for Hallmarks, which should be ‘H’).

msigdbr::msigdbr_collections() |> kableExtra::kbl() |> kableExtra::kable_styling() |> kableExtra::scroll_box(height = "300px")
gs_collection gs_subcollection gs_collection_name num_genesets
C1 Positional 302
C2 CGP Chemical and Genetic Perturbations 3494
C2 CP Canonical Pathways 19
C2 CP:BIOCARTA BioCarta Pathways 292
C2 CP:KEGG_LEGACY KEGG Legacy Pathways 186
C2 CP:KEGG_MEDICUS KEGG Medicus Pathways 658
C2 CP:PID PID Pathways 196
C2 CP:REACTOME Reactome Pathways 1736
C2 CP:WIKIPATHWAYS WikiPathways 830
C3 MIR:MIRDB miRDB 2377
C3 MIR:MIR_LEGACY MIR_Legacy 221
C3 TFT:GTRD GTRD 505
C3 TFT:TFT_LEGACY TFT_Legacy 610
C4 3CA Curated Cancer Cell Atlas gene sets 148
C4 CGN Cancer Gene Neighborhoods 427
C4 CM Cancer Modules 431
C5 GO:BP GO Biological Process 7608
C5 GO:CC GO Cellular Component 1026
C5 GO:MF GO Molecular Function 1820
C5 HPO Human Phenotype Ontology 5653
C6 Oncogenic Signature 189
C7 IMMUNESIGDB ImmuneSigDB 4872
C7 VAX HIPC Vaccine Response 347
C8 Cell Type Signature 840
H Hallmark 50
SummarizedExperiment::rowData(exp_data)
DataFrame with 33026 rows and 5 columns
             gene_name                gene_id gene_length_kb
           <character>            <character>      <numeric>
5S_rRNA        5S_rRNA ENSG00000201285, ENS..       0.621923
7SK                7SK ENSG00000232512, ENS..       6.147750
A1BG              A1BG        ENSG00000121410       8.321000
A1BG-AS1      A1BG-AS1        ENSG00000268895       7.432000
A1CF              A1CF        ENSG00000148584      86.266000
...                ...                    ...            ...
snoU109        snoU109 ENSG00000238410, ENS..       0.133714
snoU13          snoU13 ENSG00000238294, ENS..       0.105013
snoZ185        snoZ185        ENSG00000252672       0.086000
snoZ5            snoZ5        ENSG00000251721       0.084000
yR211F11.2  yR211F11.2        ENSG00000213076       0.879000
                 gene_description   gene_biotype
                      <character>    <character>
5S_rRNA    RNA, 5S ribosomal ps..           rRNA
7SK        novel transcript, lo..        lincRNA
A1BG       alpha-1-B glycoprote.. protein_coding
A1BG-AS1   A1BG antisense RNA 1..      antisense
A1CF       APOBEC1 complementat.. protein_coding
...                           ...            ...
snoU109    , small Cajal body-s..         snoRNA
snoU13     NA, , small nucleola..         snoRNA
snoZ185                        NA         snoRNA
snoZ5                          NA         snoRNA
yR211F11.2 SNARE Vti1a-beta pro..     pseudogene
pathways <- get_annotation_collection(pathway_collections, 
                                      species = species)
-- Collecting CP:KEGG_LEGACY from MSigDB...
pathway_scores <- score_pathways(exp_data, pathways, verbose = FALSE)
! Duplicated gene IDs removed from gene set KEGG_ALLOGRAFT_REJECTION
! Duplicated gene IDs removed from gene set KEGG_ANTIGEN_PROCESSING_AND_PRESENTATION
! Duplicated gene IDs removed from gene set KEGG_ASTHMA
! Duplicated gene IDs removed from gene set KEGG_AUTOIMMUNE_THYROID_DISEASE
! Duplicated gene IDs removed from gene set KEGG_CELL_ADHESION_MOLECULES_CAMS
! Duplicated gene IDs removed from gene set KEGG_GRAFT_VERSUS_HOST_DISEASE
! Duplicated gene IDs removed from gene set KEGG_HEMATOPOIETIC_CELL_LINEAGE
! Duplicated gene IDs removed from gene set KEGG_INTESTINAL_IMMUNE_NETWORK_FOR_IGA_PRODUCTION
! Duplicated gene IDs removed from gene set KEGG_LEISHMANIA_INFECTION
! Duplicated gene IDs removed from gene set KEGG_LYSOSOME
! Duplicated gene IDs removed from gene set KEGG_NATURAL_KILLER_CELL_MEDIATED_CYTOTOXICITY
! Duplicated gene IDs removed from gene set KEGG_SYSTEMIC_LUPUS_ERYTHEMATOSUS
! Duplicated gene IDs removed from gene set KEGG_TYPE_I_DIABETES_MELLITUS
! Duplicated gene IDs removed from gene set KEGG_VASCULAR_SMOOTH_MUSCLE_CONTRACTION
! Duplicated gene IDs removed from gene set KEGG_VIRAL_MYOCARDITIS
S4Vectors::metadata(exp_data)[["pathway_scores"]] <- pathway_scores

collections <- pathway_collections |> 
  paste(collapse = "_") |>
  stringr::str_remove("\\:")

plot_pathway_heatmap(exp_data, annotations = plot_annotations,
                    fwidth = 9,
                    fname = stringr::str_glue(
                      "results/pathways/hm_paths_{collections}_top20.pdf")
                    )
Warning in prep_scores_hm(exp_data, pathway_scores, pathways): 'sample_id'
already exists in colData and will be overwritten.
-- Saving heatmap at results/pathways/hm_paths_CPKEGG_LEGACY_top20.pdf

write.csv(pathways, file = stringr::str_glue("results/pathways/paths_{collections}.csv"))

Microenvironment scores

This step calculates immune and stromal cell type abundances using MCPcounter or mMCPcounter. It helps to infer the composition of the tumor microenvironment or similar contexts.

mcp_scores <- mcp_counter(exp_data, species = species)
S4Vectors::metadata(exp_data)[["microenv_scores"]] <- mcp_scores

plot_microenv_heatmap(exp_data, annotations = c("dex", "exp_cluster"),
                      fname = "results/tme/heatSmap_mcpcounter.pdf")
Warning in prep_scores_hm(exp_data, microenv_scores): 'sample_id' already
exists in colData and will be overwritten.
-- Saving heatmap at results/tme/heatSmap_mcpcounter.pdf

write.csv(mcp_scores, file = "results/tme/scores_mcpcounter.csv")

Targeted plots

This section focuses on visualizing specific genes or pathways of interest, as specified in the parameters.

Heatmaps

Generates heatmaps for pre-selected genes of interest to observe their expression across samples or conditions.

hms <- lapply(1:length(heatmap_genes), function(i) {
  gene_annot <- SummarizedExperiment::rowData(exp_data)
  genes <- heatmap_genes[[i]]
  name <- ifelse(is.null(names(heatmap_genes)), i, names(heatmap_genes)[i])
  plot_exp_heatmap(exp_data, genes = genes, 
                   annotations = plot_annotations,
                   fname = stringr::str_glue("results/targeted/hm_genes_{i}.pdf"))
})
-- Saving heatmap at results/targeted/hm_genes_1.pdf
-- Saving heatmap at results/targeted/hm_genes_2.pdf
patchwork::wrap_plots(hms, ncol = 2, guides = "collect")

Selected pathways

valid_pathways <- intersect(heatmap_pathways, rownames(pathway_scores))

plot_pathway_heatmap(exp_data, 
                     annotations = plot_annotations, 
                     pathways = valid_pathways,
                     fname = stringr::str_glue("results/targeted/hm_pathways_selected.pdf"))
Warning in prep_scores_hm(exp_data, pathway_scores, pathways): 'sample_id'
already exists in colData and will be overwritten.
-- Saving heatmap at results/targeted/hm_pathways_selected.pdf

Boxplots

Boxplots provide a clear comparison of expression levels across experimental groups or conditions.

Selected genes

genes <- boxplot_genes
annotations <- plot_annotations

boxplots <- lapply(genes, function(gene) {
  lapply(annotations, function(annotation) {
    plt <- plot_exp_boxplot(exp_data, gene = gene, 
                   annotation = annotation, 
                   color_var = annotation, 
                   pt_size = 2,
                   fname = stringr::str_glue("results/targeted/boxplots/box_{gene}_{annotation}.pdf"))
  })
}) |> purrr::flatten()
patchwork::wrap_plots(boxplots, nrows = round(length(boxplots)/2), guides = "collect")

Selected pathways

paths <- boxplot_pathways
annotations <- plot_annotations

boxplots <- lapply(paths, function(path) {
  lapply(annotations, function(annotation) {
    plt <- plot_path_boxplot(exp_data, 
                             pathway = path,
                   annotation = annotation, 
                   color_var = annotation, 
                   pt_size = 2,
                   fname = stringr::str_glue("results/targeted/boxplots/box_{path}_{annotation}.pdf"))
  })
}) |> purrr::flatten()
patchwork::wrap_plots(boxplots, nrows = round(length(boxplots)/2), guides = "collect")

Correlations

This section visualizes relationships between pairs of genes or pathways by plotting their expression/activity correlations. Correlation analysis can reveal important co-regulation or interaction patterns, helping to uncover biologically meaningful relationships.

Selected genes

Here we plot the correlation between selected gene pairs across the dataset. Each pair is plotted separately, and color-coded by sample annotation.

gene_pairs <- correlation_genes
annotations <- plot_annotations

cor_plts <- lapply(gene_pairs, function(gene_pair) {
  lapply(annotations, function(annot) {
      plot_exp_scatter(exp_data, 
                   gene1 = gene_pair[1],
                   gene2 = gene_pair[2], 
                   color_var = annot,
                   fname = stringr::str_glue(
                     "results/targeted/correlations/cor_{gene_pair[1]}_{gene_pair[2]}_color={annot}.pdf"))
  })
}) |> purrr::flatten()
-- Saving plot at results/targeted/correlations/cor_FKBP5_TSC22D3_color=dex.pdf
-- Saving plot at results/targeted/correlations/cor_FKBP5_ZBTB16_color=dex.pdf
patchwork::wrap_plots(cor_plts, nrows = round(length(cor_plts)/2), guides = "collect")

Selected pathways

Correlation plots for selected pathways can help identify similarities or differences in pathway activity patterns across samples. Each pathway pair is plotted separately and color-coded by sample annotation to illustrate trends within each condition.

path_pairs <-correlation_pathways
annotations <- plot_annotations

cor_plts <- lapply(path_pairs, function(path_pair) {
  lapply(annotations, function(annot) {
      plot_path_scatter(exp_data, 
                   pathway1 = path_pair[1],
                   pathway2 = path_pair[2], 
                   color_var = annot,
                   fname = stringr::str_glue(
                     "results/targeted/correlations/cor_{path_pair[1]}_{path_pair[2]}_color={annot}.pdf"))
  })
}) |> purrr::flatten()
-- Saving plot at results/targeted/correlations/cor_KEGG_APOPTOSIS_KEGG_GLUTATHIONE_METABOLISM_color=dex.pdf
-- Saving plot at results/targeted/correlations/cor_KEGG_APOPTOSIS_KEGG_NOD_LIKE_RECEPTOR_SIGNALING_PATHWAY_color=dex.pdf
patchwork::wrap_plots(cor_plts, nrows = round(length(cor_plts)/2), guides = "collect")

Cluster using metadata

types = names(metadata_clusters)

for(type in types) {
  exp_data <- cluster_metadata(exp_data, 
                   metadata_name = type, 
                   k = metadata_clusters[[type]]$k, 
                   features = metadata_clusters[[type]]$features,
                   n_pcs = 3 )
}

Save SummarizedExperiment

The final step saves the processed dataset and results. This ensures all outputs can be revisited or shared for further analysis.

saveRDS(exp_data, file = stringr::str_glue("results/data_SummarizedExp_{lubridate::today()}.RDS"))

Report parameters

For reproducibility, the parameters used in the analysis and the computational environment details are documented.

sessionInfo

The sessionInfo() prints out all packages loaded at the time of analysis, as well as their versions.

sessionInfo()
R version 4.5.0 (2025-04-11)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.2 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0 
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Europe/Paris
tzcode source: system (glibc)

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
 [1] factoextra_1.0.7            ggplot2_3.5.2              
 [3] biomaRt_2.64.0              CAIBIrnaseq_1.0.0          
 [5] R.utils_2.13.0              R.oo_1.27.1                
 [7] R.methodsS3_1.8.2           airway_1.28.0              
 [9] SummarizedExperiment_1.38.1 Biobase_2.68.0             
[11] GenomicRanges_1.60.0        GenomeInfoDb_1.44.0        
[13] IRanges_2.42.0              S4Vectors_0.46.0           
[15] BiocGenerics_0.54.0         generics_0.1.3             
[17] MatrixGenerics_1.20.0       matrixStats_1.5.0          

loaded via a namespace (and not attached):
  [1] ggplotify_0.1.2             filelock_1.0.3             
  [3] tibble_3.2.1                graph_1.86.0               
  [5] XML_3.99-0.18               lifecycle_1.0.4            
  [7] httr2_1.1.2                 rstatix_0.7.2              
  [9] lattice_0.22-5              crosstalk_1.2.1            
 [11] backports_1.5.0             magrittr_2.0.3             
 [13] plotly_4.10.4               rmarkdown_2.29             
 [15] yaml_2.3.10                 rlist_0.4.6.2              
 [17] cowplot_1.1.3               DBI_1.2.3                  
 [19] RColorBrewer_1.1-3          lubridate_1.9.4            
 [21] abind_1.4-8                 purrr_1.0.4                
 [23] msigdbr_10.0.2              yulab.utils_0.2.0          
 [25] rappdirs_0.3.3              GenomeInfoDbData_1.2.14    
 [27] ggrepel_0.9.6               irlba_2.3.5.1              
 [29] tidytree_0.4.6              GSVA_2.2.0                 
 [31] MCPcounter_1.2.0            annotate_1.86.0            
 [33] svglite_2.1.3               codetools_0.2-20           
 [35] DelayedArray_0.34.1         xml2_1.3.8                 
 [37] tidyselect_1.2.1            aplot_0.2.5                
 [39] UCSC.utils_1.4.0            farver_2.1.2               
 [41] ScaledMatrix_1.16.0         BiocFileCache_2.16.0       
 [43] jsonlite_2.0.0              Formula_1.2-5              
 [45] systemfonts_1.2.3           tools_4.5.0                
 [47] progress_1.2.3              treeio_1.32.0              
 [49] ragg_1.4.0                  Rcpp_1.0.14                
 [51] glue_1.8.0                  gridExtra_2.3              
 [53] SparseArray_1.8.0           xfun_0.52                  
 [55] DESeq2_1.48.0               dplyr_1.1.4                
 [57] HDF5Array_1.36.0            withr_3.0.2                
 [59] fastmap_1.2.0               rhdf5filters_1.20.0        
 [61] digest_0.6.37               rsvd_1.0.5                 
 [63] timechange_0.3.0            R6_2.6.1                   
 [65] gridGraphics_0.5-1          textshaping_1.0.1          
 [67] RSQLite_2.3.10              h5mread_1.0.0              
 [69] tidyr_1.3.1                 data.table_1.17.0          
 [71] prettyunits_1.2.0           httr_1.4.7                 
 [73] htmlwidgets_1.6.4           S4Arrays_1.8.0             
 [75] pkgconfig_2.0.3             gtable_0.3.6               
 [77] progeny_1.30.0              blob_1.2.4                 
 [79] SingleCellExperiment_1.30.0 XVector_0.48.0             
 [81] htmltools_0.5.8.1           carData_3.0-5              
 [83] fgsea_1.34.0                msigdbdf_24.1.0            
 [85] GSEABase_1.70.0             kableExtra_1.4.0           
 [87] scales_1.4.0                tidyverse_2.0.0            
 [89] png_0.1-8                   SpatialExperiment_1.18.0   
 [91] ggfun_0.1.8                 knitr_1.50                 
 [93] rstudioapi_0.17.1           tzdb_0.5.0                 
 [95] reshape2_1.4.4              rjson_0.2.23               
 [97] nlme_3.1-168                curl_6.2.2                 
 [99] cachem_1.1.0                rhdf5_2.52.0               
[101] stringr_1.5.1               parallel_4.5.0             
[103] vipor_0.4.7                 AnnotationDbi_1.70.0       
[105] pillar_1.10.2               grid_4.5.0                 
[107] vctrs_0.6.5                 ggpubr_0.6.0               
[109] BiocSingular_1.24.0         car_3.1-3                  
[111] dbplyr_2.5.0                beachmat_2.24.0            
[113] xtable_1.8-4                beeswarm_0.4.0             
[115] evaluate_1.0.3              readr_2.1.5                
[117] magick_2.8.6                cli_3.6.5                  
[119] locfit_1.5-9.12             compiler_4.5.0             
[121] rlang_1.1.6                 crayon_1.5.3               
[123] ggsignif_0.6.4.9000         labeling_0.4.3             
[125] plyr_1.8.9                  forcats_1.0.0              
[127] fs_1.6.6                    ggbeeswarm_0.7.2           
[129] stringi_1.8.7               viridisLite_0.4.2          
[131] BiocParallel_1.42.0         assertthat_0.2.1           
[133] babelgene_22.9              Biostrings_2.76.0          
[135] lazyeval_0.2.2              ggheatmapper_0.2.1         
[137] Matrix_1.7-3                hms_1.1.3                  
[139] patchwork_1.3.0             sparseMatrixStats_1.20.0   
[141] bit64_4.6.0-1               Rhdf5lib_1.30.0            
[143] KEGGREST_1.48.0             broom_1.0.8                
[145] memoise_2.0.1               ggtree_3.16.0              
[147] fastmatch_1.1-6             bit_4.6.0                  
[149] ape_5.8-1